package bancosys.tec.rtm.impl.diagnostician;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.HashMap;
import java.util.Map;

import bancosys.tec.rtm.diagnostician.AbstractDiagnostician;
import bancosys.tec.rtm.diagnostician.DiagnosisBean;

/**
 * Diagnoses CPU usage for each thread - both total CPU usage and CPU usage on user mode
 * 
 * @author takeshi
 */
public class ThreadCPUUsageDiagnostician extends AbstractDiagnostician {

    private static final int ONE_SECOND = 1000;

    /**
     * {@inheritDoc}
     */
    public String getName() {
        return "Thread CPU Usage Diagnostician";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean fillDiagnosisBean(DiagnosisBean bean) throws Exception {
        ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
        if (!mxBean.isThreadCpuTimeEnabled() || !mxBean.isThreadCpuTimeSupported()) {
            return false;
        }
        long[] threadIds = mxBean.getAllThreadIds();
        Map<Long, ThreadTime> timeMap = new HashMap<Long, ThreadCPUUsageDiagnostician.ThreadTime>(threadIds.length);
        for (long threadId : threadIds) {
            timeMap.put(threadId, new ThreadTime(threadId, mxBean));
        }
        Thread.sleep(ONE_SECOND);
        double totalCpu = 0.0, totalUser = 0.0;
        for (long threadId : threadIds) {
            ThreadTime now = new ThreadTime(threadId, mxBean);
            if (now.isValid() && timeMap.containsKey(threadId)) {
                double threadCpuUsage = timeMap.get(threadId).getCPUUsagePercentage(now);
                double threadUserMode = timeMap.get(threadId).getUserModePercentage(now);
                bean.addDescription("CPU Usage Information for: " + now.name);
                bean.addDescription("CPU usage (total): " + threadCpuUsage + "%");
                bean.addDescription("CPU usage (user mode): " + threadUserMode + "%");
                totalCpu += threadCpuUsage;
                totalUser += threadUserMode;
            }
        }
        bean.addDescription("Total CPU Usage: " + totalCpu + "%");
        bean.addDescription("Total User Usage: " + totalUser + "%");
        return true;
    }

    /**
     * Probes and keeps timing information for a single thread
     * 
     * @author takeshi
     */
    private static final class ThreadTime {

        private static final double NUM_100 = 100.0;

        private final long wallTime, userTime, cpuTime;

        private final String name;

        /**
         * Constructor
         * 
         * @param threadId long
         * @param bean bean
         */
        private ThreadTime(long threadId, ThreadMXBean bean) {
            this.wallTime = System.nanoTime();
            this.userTime = bean.getThreadUserTime(threadId);
            this.cpuTime = bean.getThreadCpuTime(threadId);
            this.name = bean.getThreadInfo(threadId).getThreadName();
        }

        /**
         * Calculates total cpu usage for the given thread. This {@link ThreadTime} must be created before the given {@link ThreadTime}
         * 
         * @param now {@link ThreadTime} the most recent {@link ThreadTime}
         * @return percentage of total CPU usage
         */
        public double getCPUUsagePercentage(ThreadTime now) {
            double diff = now.wallTime == this.wallTime ? 1.0 : now.wallTime - this.wallTime;
            double elapsed = now.cpuTime - this.cpuTime;
            return NUM_100 * elapsed / diff;
        }

        /**
         * Calculates cpu usage for the given thread on user mode. This {@link ThreadTime} must be created before the given
         * {@link ThreadTime}
         * 
         * @param now {@link ThreadTime} the most recent {@link ThreadTime}
         * @return percentage of CPU usage in user mode
         */
        public double getUserModePercentage(ThreadTime now) {
            double diff = now.wallTime == this.wallTime ? 1.0 : now.wallTime - this.wallTime;
            double elapsed = now.userTime - this.userTime;
            return NUM_100 * elapsed / diff;
        }

        /**
         * @return true if this measument can be used
         */
        public boolean isValid() {
            return this.userTime > 0 && cpuTime > 0;
        }

    }

}
